﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace ILSMultiPassLight
{
    public partial class Game1
    {
        #region Constants
        const int number_of_rows = 129;
        const int number_of_cols = 129;
        const int number_of_vertices = number_of_rows * number_of_cols;
        const int number_of_indices = (number_of_rows - 1) * (number_of_cols - 1) * 2 * 3;
        const float rotationAngle = 0.03f;
        #endregion

        #region Handle Input

        public void HandleInput()
        {
            //Press Esc To Exit
            if (currentKeys.IsKeyDown(Keys.Escape))
                this.Exit();

            //Press Directional Keys to rotate camera
            if (currentKeys.IsKeyDown(Keys.Left))
            {
                cameraPos = Vector3.Transform(cameraPos, Matrix.CreateRotationY(-0.05f));
                View = Matrix.CreateLookAt(cameraPos, Vector3.Zero, Vector3.Up);
            }
            if (currentKeys.IsKeyDown(Keys.Right))
            {
                cameraPos = Vector3.Transform(cameraPos, Matrix.CreateRotationY(0.05f));
                View = Matrix.CreateLookAt(cameraPos, Vector3.Zero, Vector3.Up);
            }


            //Increase light numbers
            if (currentKeys.IsKeyDown(Keys.Q) && oldKeys.IsKeyUp(Keys.Q))
            {
                dirLights.Add(new DirectionalLight() { Color = new Color((float)rand.NextDouble(), (float)rand.NextDouble(), (float)rand.NextDouble()) });
                DirectionalLight.ResetPositions(dirLights);
            }
            if (currentKeys.IsKeyDown(Keys.W) && oldKeys.IsKeyUp(Keys.W))
            {
                pointLights.Add(new PointLight() { Color = new Color((float)rand.NextDouble(), (float)rand.NextDouble(), (float)rand.NextDouble()) });
                PointLight.ResetPositions(pointLights);
            }
            if (currentKeys.IsKeyDown(Keys.E) && oldKeys.IsKeyUp(Keys.E))
            {
                spotLights.Add(new SpotLight() { Color = new Color((float)rand.NextDouble(), (float)rand.NextDouble(), (float)rand.NextDouble()) });
                SpotLight.ResetPositions(spotLights);
            }

            //Decrease light numbers
            if (currentKeys.IsKeyDown(Keys.A) && oldKeys.IsKeyUp(Keys.A))
            {
                if (dirLights.Count > 0) dirLights.RemoveAt(dirLights.Count - 1);
                DirectionalLight.ResetPositions(dirLights);
            }
            if (currentKeys.IsKeyDown(Keys.S) && oldKeys.IsKeyUp(Keys.S))
            {
                if (pointLights.Count > 0) pointLights.RemoveAt(pointLights.Count - 1);
                PointLight.ResetPositions(pointLights);
            }
            if (currentKeys.IsKeyDown(Keys.D) && oldKeys.IsKeyUp(Keys.D))
            {
                if (spotLights.Count > 0) spotLights.RemoveAt(spotLights.Count - 1);
                SpotLight.ResetPositions(spotLights);
            }

        }
        #endregion

        #region Create the terrain VertexBuffer


        VertexBuffer vertexBuffer;

        void CreateVertexBuffer()
        {
            VertexPositionNormalTexture[] vertices = new VertexPositionNormalTexture[number_of_vertices];

            float halfWidth = (number_of_cols - 1) * 0.5f;
            float halfDepth = (number_of_rows - 1) * 0.5f;

            float du = 1.0f / (number_of_cols - 1);
            float dv = 1.0f / (number_of_rows - 1);
            for (int i = 0; i < number_of_rows; ++i)
            {
                float z = halfDepth - i;
                for (int j = 0; j < number_of_cols;++j)
                {
                    float x = -halfWidth + j;

                    float y = getHeight(x, z);

                    vertices[i * number_of_cols + j].Position = new Vector3(x, y, z);

                    vertices[i * number_of_cols + j].TextureCoordinate = new Vector2(j * du, i * dv);

                    Vector3 normal = new Vector3();
                    normal.X = -0.03f * z * (float)Math.Cos(0.1f * x) - 0.3f * (float)Math.Cos(0.1f * z);
                    normal.Y = 1;
                    normal.Z = -0.3f * (float)Math.Sin(0.1f * x) + 0.03f * x *(float)Math.Sin(0.1f * z);
                    normal.Normalize();
                    vertices[i * number_of_cols + j].Normal = normal;
                }
            }

            vertexBuffer = new VertexBuffer(GraphicsDevice, VertexPositionNormalTexture.VertexDeclaration, number_of_vertices, BufferUsage.WriteOnly);
            vertexBuffer.SetData<VertexPositionNormalTexture>(vertices);
        }

        private float getHeight(float x, float z)
        {
            return 0.3f*(z*(float)Math.Sin(0.1f*x)+x*(float)Math.Cos(0.1f*z));
        }
        #endregion

        #region Create the terrain IndexBuffer
        
        IndexBuffer indexBuffer;

        void CreateIndexBuffer()
        {
            int[] indices = new int[number_of_indices];
            int k = 0;
            for (int i = 0; i < number_of_rows - 1; ++i)
            {
                for (int j = 0; j < number_of_cols - 1; ++j)
                {
                    indices[k]   = i * number_of_cols + j;
                    indices[k+1] = (i+1) * number_of_cols + j;
                    indices[k + 2] = i * number_of_cols + j + 1;

                    indices[k+3] = (i + 1) * number_of_cols + j;
                    indices[k + 4] =  (i + 1) * number_of_cols + j + 1;
                    indices[k + 5] = i * number_of_cols + j + 1;

                    k += 6;
                }
            }

            indexBuffer = new IndexBuffer(GraphicsDevice, IndexElementSize.ThirtyTwoBits, number_of_indices, BufferUsage.WriteOnly);
            indexBuffer.SetData<int>(indices);

        }
        #endregion

        #region Light Structures
        
        class DirectionalLight
        {
            public Vector3 Direction;
            public Color Color;

            public static void Update(List<DirectionalLight> lights)
            {
                Matrix rot = Matrix.CreateRotationZ(rotationAngle);
                foreach (DirectionalLight light in lights)
                    light.Direction = Vector3.Transform(light.Direction, rot);
            }

            public static void ResetPositions(List<DirectionalLight> lights)
            {
                int num = lights.Count;
                Vector3 origin = Vector3.Down;

                for (int i = 0; i < num; i++)
                {
                    lights[i].Direction = Vector3.TransformNormal(origin, Matrix.CreateRotationZ(MathHelper.TwoPi / num * i));
                    lights[i].Direction.Normalize();
                }
            }
        }

        class PointLight
        {
            public Vector3 Position;
            public Color Color;

            public static void Update(List<PointLight> lights)
            {
                Matrix rot = Matrix.CreateRotationY(rotationAngle);
                foreach (PointLight light in lights)
                    light.Position = Vector3.Transform(light.Position, rot);
            }

            public static void ResetPositions(List<PointLight> lights)
            {
                int num = lights.Count;
                Vector3 origin = new Vector3(0, 15, 30);

                for (int i = 0; i < num; i++)
                {
                    lights[i].Position = Vector3.Transform(origin, Matrix.CreateRotationY(MathHelper.TwoPi / num * i));
                }
            }
        }

        class SpotLight
        {
            public Vector3 Position;
            public Vector3 Direction;
            public Color Color;

            public static void Update(List<SpotLight> lights)
            {
                Matrix rot = Matrix.CreateRotationY(rotationAngle);
                foreach(SpotLight light in lights)
                    light.Direction = Vector3.Transform(light.Direction, rot);
            }

            public static void ResetPositions(List<SpotLight> lights)
            {
                int num = lights.Count;
                Vector3 origin = new Vector3(0, -1, -1);
                Vector3 spotPos = new Vector3(0, 30, 0);

                for (int i = 0; i < num; i++)
                {
                    lights[i].Position = spotPos;
                    lights[i].Direction = Vector3.TransformNormal(origin, Matrix.CreateRotationY(MathHelper.TwoPi / num * i));
                    lights[i].Direction.Normalize();
                }
            }
        }
        #endregion
    }
}
